package ServerDB;
import DataConnectionInterface.*;
import DataConnectionInterface.*;

import java.io.InputStream;
import Helper.GameException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class ClientConnection implements Runnable {

	public ClientConnection(Socket clientSocket, GameState gameState,
			int playerNumber, NotifyMonitor monitor) {
		this.monitor = monitor;
		this.gameState = gameState;
		this.clientSocket = clientSocket;
		this.playerNumber = playerNumber;
	}
	
	public void sendHighScores(){
		try{
			// get number of high scores to show 
			int maxNumOfUsers = (Integer) ois.readObject();
			
			// get high scores from database 
			gameConn = connPool.getConnection();
			LinkedList<UserInfo> users = (LinkedList<UserInfo>) gameConn.getHighScores(maxNumOfUsers);
			connPool.returnConnection(gameConn);
			oos.writeObject(new Integer(users.size()));
			
			// send users to player
			for(UserInfo user: users)
				oos.writeObject(new UserInfo(user));
			
		} catch(IOException e){
			
		} catch (ClassNotFoundException e) {
			
		} catch (GameException e) {
			
		}
		
	}

	public void run() {

		// now get client's requests

		try {
			connPool = ConnectionPool.getInstance();
			
			OutputStream os = clientSocket.getOutputStream();
			oos = new ObjectOutputStream(os);

			InputStream is = clientSocket.getInputStream();
			ois = new ObjectInputStream(is);
			
			// helps to choose randomly the beginner of the game
			Random gen = new Random(1290);
			gameState.setRand(gen.nextInt(), playerNumber);
			
			// verify user
			boolean accessePermitted;
			UserInfo user = (UserInfo) ois.readObject();
			accessePermitted = verifyUser(user);
			oos.writeObject(new Boolean(accessePermitted));
			
			// if user not permitted, quit
			if(!accessePermitted) {
				closeConnection(DisconnectionType.SINGLE);
				return;
			}
			
			// otherwise, user was accepted
			gameState.setUser(user, playerNumber);
			
			// send to user list of worlds
		
			LinkedList<UniverseInformation> list = null;
			int numCardsForGame = Server.numCards;
			
			gameConn = connPool.getConnection();
			list = (LinkedList<UniverseInformation>)gameConn.getLargeUniverses(numCardsForGame);
			connPool.returnConnection(gameConn);
			
			oos.writeObject(new Integer(list.size()));
			for (UniverseInformation universe: list)
				oos.writeObject(new String(universe.getName()));
			
			
			boolean chosenWorld = false;
			while (!chosenWorld) {
				
				String command = (String) ois.readObject();
				if(command.equals("HIGH SCORES")){
					// user requires high scores table
					sendHighScores();
					continue;
				}
				
				
				// get chosen world from user
				String chosenUniverse = (String) ois.readObject();
				chosenWorld = true;

				// send cards of chosen world to user
				gameConn = connPool.getConnection();
				List<Card> cards = gameConn.getCardsFromUniverse(chosenUniverse, numCardsForGame);
				connPool.returnConnection(gameConn);
				oos.writeObject(new Integer(cards.size()));
				for (Card card : cards)
					oos.writeObject(new Card(card));


				clientSocket.setSoTimeout(100);
				boolean cancel = false;

				// player is ready now for the game
				gameState.setPlayerReadiness(playerNumber, true);
				while (true) {

					try {
						cancel = (Boolean) ois.readObject();
					} catch (SocketTimeoutException e) {
						
					}

					if (cancel) { // stop waiting for players
						oos.writeObject(new Boolean(false));
						chosenWorld = false;
						clientSocket.setSoTimeout(0);
						gameState.setPlayerReadiness(playerNumber, false);
						break;
					}

					// check for a ready player 
					if (gameState.isPlayerReady(1 - playerNumber)) {
						oos.writeObject(new Boolean(true));
						oos.writeObject(new Boolean(gameState.getBeginner(playerNumber))); // who starts
						break;
					}
					Thread.sleep(100);
				}
			}
		} catch (Exception e) {
			closeConnection(DisconnectionType.SINGLE);
			return;
		}

		// now the game starts...
		
		boolean init = false;
		try {
			Card card;
			int numOfCards;
			int count;
			String gameStatus;
			while(true){
				count = 0;
				while(!gameState.isUpdated(1 - playerNumber) && init)
						Thread.sleep(100);
				
				
				numOfCards = init ? gameState.getNumOfCards(1 - playerNumber) : 1; 
				// send to player number of rival's cards
				oos.writeObject(new Integer(numOfCards));
				
				// get game status
				while(true){
					try{
						gameStatus = (String) ois.readObject();
						gameState.setStatus(gameStatus, playerNumber);
						if(!gameStatus.equals("NONE")){
							closeConnection(DisconnectionType.QUIT);
							return;
						}
						else break;
					}
					catch(SocketTimeoutException e){
					}
				}
				
				init = true;
				while (true) {
					
					try {
						// receive card from player
						card = (Card) ois.readObject();
						
						gameState.setPlayerCard(card, playerNumber);
						if(++count == 2) break;
					} catch (SocketTimeoutException e) {
						gameState.setPlayerCard(null, playerNumber);
					}
	
					if ((card = gameState.getPlayerMove(1 - playerNumber)) != null) {
						// send one player's card to other player
						oos.writeObject(new Card(card));
						if(++count == 2) break;
					}
	
					if (Thread.interrupted()){
						closeConnection(null);
						return;
					}
				}
				
				// get number of player's cards
				numOfCards = (Integer) ois.readObject();
				gameState.setNumOfCards(numOfCards, playerNumber);
			}
		} catch (Exception e) {
			closeConnection(DisconnectionType.MULTI);
		}
		
	}
	
	private boolean verifyUser(UserInfo user){
		// verifies users's password or creates new account
		try {
			gameConn = connPool.getConnection();
			boolean accepted = gameConn.authenticateUser(user);
			connPool.returnConnection(gameConn);
			return accepted;
		} catch (GameException e) {
			
		}
		return false;
	}
	
	private void closeConnection(DisconnectionType type) {
		try {
		
			gameState.setPlayerCard(null, playerNumber);
			gameState.setPlayerReadiness(playerNumber, false);
			if (oos != null)
				oos.close();
			if (ois != null)
				ois.close();
			clientSocket.close();
			
			if (type != null)
				monitor.setNotified(playerNumber, type);

		} catch (Exception e) {
			
		}
	}

	private NotifyMonitor monitor;
	private GameDataConnectionInterface gameConn;
	private ConnectionPool connPool;
	private Socket clientSocket;
	private int playerNumber;
	private GameState gameState;
	private ObjectOutputStream oos = null;
	private ObjectInputStream ois = null;
}
